文章目录
  1. 1. 第38条:检查参数的有效性
  2. 2. 第39条:必要时进行保护性拷贝
  3. 3. 第40条:谨慎设计方法签名
  4. 4. 第41条:慎用重载
  5. 5. 第42条:慎用可变参数
  6. 6. 第43条:返回长度的数组或者集合,而不是null
  7. 7. 第44条:为所有导出的API元素编写文档注释

本章主要讨论方法设计的几个方面:

  • 如何处理参数和返回值
  • 如何设计方法签名
  • 如何为方法编写文档

第38条:检查参数的有效性

这条就是教我们对于公有方法,最好在方法体上的开头处加上参数检查,并且在文档中清楚地指出这些限制,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 求模
* @param m
* @return
* @throws ArithmeticException 如何m小于等于0时抛出异常
*/

public BigInteger mod(BigInteger m) throws ArithmeticException
{

if(m.signum()<=0)
throw new ArithmeticException("Moduls <= 0:"+m);

//do something
return ...
}

假如是对于私有方法,通常使用断言来检查他们的参数

1
2
3
4
5
6
private void sort(long a[],int offset,int length)
{

assert a!=null:"排序数组不能为空";
assert offset >= 0&& offset<=a.length;
assert length>=0 && length<=a.length-offset;
}

assert默认是关闭的,需要使用-enableassertions或者-eaJava参数来显示开启。

其实并不是对任何参数都加限制是件好事,因为参数的有效性检查会带来开销,我们应该在设计方法时,应该使他们尽可能的通用,并符合实际的需要。

第39条:必要时进行保护性拷贝

假设类的客户端会尽其所能的破坏这个类的约束条件,因此你必须保护性的设计程序。
下面希望设计的是一个不可能修改对象内部状态的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final class Perion{
private final Date start;
private final Date end;

public Period(Date start,Date end) throws IllegalArgumentException
{

if(start.compareTo(end)>0)
throw new IllegalArgumentException("开始时间大于结束时间");

this.start=start;
this.end=end;
}

public Date start()
{

return this.start;
}

public Date end()
{

return this.end;
}

}

上面的类Perion似乎是不可变的,并且还加强了约束条件,
但是Date这个类是引用类型,如果是这么使用这个类:

1
2
3
4
Date start=new Date();
Date end=new Date();
Perion p=new Perion(start,end);
start.setYear(78);//在这里进行了外部对Perion类内部对象的修改

你可以发现实例化之后的Date会被修改掉,所以你需要保护性拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public final class Perion{
private final Date start;
private final Date end;

public Period(Date start,Date end) throws IllegalArgumentException
{

//一定要先赋值 在进行类型检查
this.start=new Date(start.getTime());
this.end=new Date(end.getTime());

if(this.start.compareTo(this.end)>0)
throw new IllegalArgumentException("开始时间大于结束时间");
}

public Date start()
{

//防止他们使用get方法来获取引用对象
return new Date(this.start.getTime());
}

public Date end()
{

return new Date(this.end.getTime());
}
}

但是可以发现这种方法虽然是安全了,但是代价较大,另一种更好地方法是不要用Date引用对象存储再数据库,而是使用时间戳更加合适

1
2
private final long start;//其他就不多说了
private final long end;

简而言之,如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性的拷贝这些组件。

第40条:谨慎设计方法签名

设计方法的时候有下面三个设计原则:

  • 谨慎地选择方法名称

    方法的名称应该始终遵循标准的命名习惯

  • 不要过于追求便利的方法

    每个方法应该尽其所能,方法定义也不要太多,最好文档化,有测试和维护,还有接口优于类

  • 避免过长的参数列表
    1. 把方法分解成多个方法,每个方法只需要这些参数的一个子集
    2. 创建辅助类,其实就是实体类,相信大家都懂的
    3. 采用Builder模式

第41条:慎用重载

对于重载方法的选择是静态的,而对于被覆盖方法的选择是动态的。

使用重载方法可能会让你在调用的时候产生混淆,普通用户根本不知道“对于一组给定的参数,其中哪个重载方法会被调用”。

例如:ArrayList有一个构造器带一个int参数,另一个构造器带Collection参数,难以想象的情况下会不清楚调用哪个参数。

在Java1.5之前,所有的基本类型都是根本不同于所有的引用类型,但是当自动装箱出现之后,就不再如此了,他会带来真正的麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args)
{

Set<Integer> set=new TreeSet<Integer>();
List<Integer> list=new ArrayList<Integer>();

for(int i=-3;i<3;i++)
{
set.add(i);
list.add(i);
}

for(int i=0;i<3;i++)
{
set.remove(i);
list.remove(i);
}

System.out.println(set+" "+list);
}

当初写这段代码的本意可能使希望添加[-3~3]之后再移除[0~2],但是看结果你会发现

[-3, -2, -1] [-3, -2, -1]

关于上面的输出:

  • set.remove(i)调用的是重写方法remove(E),这里的E集合是Integer的元素类型,将i自动装箱到Integer
  • 但是list.remove(i)重载的却是remove(int index),它是按索引去移除元素,也因此得到了意料之外的结果。

简而言之:“能够重载的方法”并不意味着就”应该重载方法”,一般情况下,对于读个具有相同参数目的的方法来说,应该重载方法。

我想,其实重载还是很好使的,但是在重载的时候一般要避免可拆装箱类型或者有继承类型的出现,比如上例中的remove(Object o)以及remove(int index)就是非常会让人混淆,你可以写成removeByIndex(int index) ^_^

第42条:慎用可变参数

在Java中,可以使用下面的语法来传递可变参数:

1
2
3
4
5
6
7
8
9
public int sum(int ... args)
{

int sum=0;
for(int i:args)
{
sum+=i;
}
return sum;
}

但是该方法可以不传参数调用,但是有时候需要编译1个或者多个参数的传入,而不是0到或者多个,得加上args.length的检查,但是更好地方法是

1
2
3
4
5
6
7
8
9
public int sum(int firstArgs,int ... args)
{

int sum=firstArgs;
for(int i:args)
{
sum+=i;
}
return sum;
}

在重视参数性能的情况下,使用可变参数机制要特别小心,可变参数的方法每次调用都会进行一次数组的分配和初始化,所以建议先重载5个参数一下的方法(上一条还建议慎用重载呢-_-):

1
2
3
4
public int sum(int arg1)
public int sum(int arg1,int arg2)
public int sum(int arg1,int arg2,int arg3)
public int sum(int arg1,int arg2,int arg3,int arg4)

简而言之,在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是它们不应该被过度滥用

第43条:返回长度的数组或者集合,而不是null

当函数的返回类型集合类但是却没有集合元素返回时不要返回null,因为开发人员可能会使用返回值来做下一个操作,所以最好你应该返回一个空得集合。

1
2
3
4
5
6
7
private static final List<Integer> EMPTY_DATA=new ArrayList<Integer>();

public static List<Integer> main(String[] args)
{

if(...)
return EMPTY_DATA;//而不是null
}

对,这个注意好,妈妈再也不用担心我这类方法出来之后有没有检查null

第44条:为所有导出的API元素编写文档注释

这还用说嘛,最讨厌使用那么没有提供注释的API了,你叫臣妾怎么顺心的用你的API啊。
一般较为友好的注释是这么写的

1
2
3
4
5
6
7
/**
* 这里写你这个方法是干嘛的,有什么特征,或者使用样例、
* @param index 这里写参数的接口
* @return 返回值,一般是一个短语
* @exception 这里就是写方法可能抛出的异常以及原因
*/

E get(int int)

这里的注释支持简单的html标签:

  • 比如你想换行或者分段可以使用<p>d</p>
  • 你想斜体就用<i>d</i>
  • 你想保持注释中文本的格式就是用<pre>

还有:

  • 使用<pre>{@code,…}</pre>来在注释中添加代码
  • 使用<pre>{@link,…}</pre>跳转到指定类额注释
  • 还有几个其他的,自己看Eclipse中的提示

简而言之,要为API编写文档,注释是最好的,最有效的途径

文章目录
  1. 1. 第38条:检查参数的有效性
  2. 2. 第39条:必要时进行保护性拷贝
  3. 3. 第40条:谨慎设计方法签名
  4. 4. 第41条:慎用重载
  5. 5. 第42条:慎用可变参数
  6. 6. 第43条:返回长度的数组或者集合,而不是null
  7. 7. 第44条:为所有导出的API元素编写文档注释